package fr.cephb.locustree;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
//import java.util.regex.Pattern;
import java.awt.Rectangle;
import java.io.File;
import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

import fr.cephb.berkeleydb.db.Walker;
import fr.cephb.locustree.svg.DefaultMappedXY;
import fr.cephb.locustree.svg.DefaultTrackHandler;
import fr.cephb.locustree.svg.EntityPainter;
import fr.cephb.locustree.svg.HistogramPainter;
import fr.cephb.locustree.svg.MappedXY;
import fr.cephb.locustree.svg.Packer;
import fr.cephb.locustree.svg.TrackHandler;
import fr.cephb.xml.SVG;
import fr.cephb.xml.XLink;

/**
 * LocusTree SVG
 * @author pierre
 *
 */
public class LTSvg
	{
	private static final Logger LOG=Logger.getLogger("fr.cephb");
	private static final float FONT_TO_PIXEL_RATIO=0.6f;//empiric value
	private static final int DEFAULT_PANE_WIDTH=800;
	private static final int MARGIN_LEFT=200;
	private static final int FEATURE_HEIGHT=20;
	private static final int LABEL_FONT_SIZE=(int)(FEATURE_HEIGHT*0.8);
	//where should we write a label ?

	
	//Regex finding URL
	//private final Pattern RS_PATTERN=Pattern.compile("rs[1-9][0-9]*");
	//private final Pattern ACN_PATTERN=Pattern.compile("(NM|NP)_[0-9]+");
	//private final Pattern ACN_PATTERN2=Pattern.compile("[A-Z][0-9A-Z]{5,7}(\\.[0-9]+)?");
	
	private Document dom;
	private Element svgRoot=null;
	private Element svgStyle=null;
	private Element svgDefs=null;
	private Element svgScript=null;
	private Element drawing;
	private LTModel model;
	private Organism organism;
	private ChromInfo chromosome;
	private Track currentTrack=null;
	private int chrom_start=0;
	private int chrom_length=0;
	//private Integer resolution=null;
	private Integer pane_width=null;
	private int max_y=100;//margin TOP
	private boolean flag_dirty=false;
	
	private Map<Integer,TrackHandler> trackid2trackhandler=new HashMap<Integer, TrackHandler>();
	
	
	
	
	/**
	 * LTScannerImpl
	 *
	 */
	private class LTScannerImpl
		implements LTScanner
		{
		Track track=null;
		LTHistogram ltHistogram;
		int max_object_length=0;
		List<MappedXY> _mappedXY=new ArrayList<MappedXY>();
		
		
		LTScannerImpl()
			{
			this.ltHistogram=new LTHistogram(
					LTSvg.this.getPaneWidth(),
					LTSvg.this.chrom_start,
					LTSvg.this.chrom_length
					);
			
			//int nbr_histogram= getPaneWidth()/DEFAULT_HISTOGRAM_WIDTH;
			//if(nbr_histogram> LTSvg.this.chrom_length)
			//	{
			//	nbr_histogram=LTSvg.this.chrom_length;
			//	}
			//this.histogram_counts=new int[nbr_histogram];
			//this.resolution=  LTSvg.this.chrom_length/this.histogram_counts.length;
			//this.resolution=  LTSvg.this.chrom_length/this.ltHistogram.size();
			//if(this.resolution==0) this.resolution=1;
			//Arrays.fill(this.histogram_counts,0);
			}
		
		@Override
		public int getStart()
			{
			return Math.max( LTSvg.this.chrom_start-max_object_length , 0);
			}
		
		@Override
		public int getTrackId()
			{
			return this.track.getId();
			}
		
		@Override
		public boolean isAbortCalled()
			{
			return false;
			}
		
		@Override
		public int getResolution()
			{
			return ltHistogram.getResolution();
			}
		
		@Override
		public int getLength()
			{
			return LTSvg.this.chrom_length+max_object_length;
			}
		
		
		
		
		@Override
		public int getChromosomeId()
			{
			return LTSvg.this.getChromosome().getId();
			}
		
		@Override
		public void entity(MappedObject entity)
			{
			LTSvg.this.entity(this,entity);
			}
		
		@Override
		public void histogram(LTNode root)
			{
			LTSvg.this.histogram(this,root);
			}
		}
	
	public LTSvg(LTModel model,
		Organism organism,
		ChromInfo chromosome,
		int chrom_start,
		int chrom_length
		)
		{
		this.model=model;
		this.organism=organism;
		this.chrom_start=chrom_start;
		this.chrom_length=chrom_length;
		this.chromosome=chromosome;
		
		
		try {
			DocumentBuilderFactory f=DocumentBuilderFactory.newInstance();
			f.setCoalescing(true);
			f.setNamespaceAware(true);
			f.setValidating(false);
			f.setExpandEntityReferences(true);
			f.setIgnoringComments(true);
			f.setIgnoringElementContentWhitespace(false);
			DocumentBuilder docBuilder= f.newDocumentBuilder();
			this.dom=docBuilder.newDocument();
			
			this.svgRoot = this.dom.createElementNS(SVG.NS,"svg");
			this.dom.appendChild(this.svgRoot );
			this.svgRoot.setAttribute("xmlns", SVG.NS);
			this.svgRoot.setAttribute("xmlns:xlink", XLink.NS);
			this.svgRoot.setAttribute("style",
				"font-size:"+LABEL_FONT_SIZE+"px;"+
				"font-family:courier;"+
				"font-weight:lighter;"+
				"font-style:normal;"+
				"stroke:black;fill:none;stroke-width:0.3px;"
				);
			
			
			Element title= this.dom.createElementNS(SVG.NS,"title");
			title.appendChild(dom.createTextNode(getChromosome().getName()+":"+(chrom_start)+"-"+(chrom_start+chrom_length)));
			this.svgRoot .appendChild(title);
			title= this.dom.createElementNS(SVG.NS,"desc");
			title.appendChild(dom.createTextNode("CNG/CEPH 2009. Created with "+getClass().getName()));
			this.svgRoot .appendChild(title);
			
			this.svgScript= this.dom.createElementNS(SVG.NS,"script");
			this.svgRoot .appendChild(this.svgScript);
			this.svgScript.setAttribute("type", "text/ecmascript");
			this.svgScript.appendChild(dom.createCDATASection("\n"));
			
			this.svgDefs= this.dom.createElementNS(SVG.NS,"defs");
			this.svgRoot .appendChild(this.svgDefs);
			Element grad= this.dom.createElementNS(SVG.NS,"linearGradient");
			this.svgDefs.appendChild(grad);
			grad.setAttribute("id", "background");
			grad.setAttribute("x1", "50%");
			grad.setAttribute("y1", "0%");
			grad.setAttribute("x2", "50%");
			grad.setAttribute("y2", "100%");
			Element stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "0%");
			stop.setAttribute("stop-color","lightgray");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "100%");
			stop.setAttribute("stop-color","gray");
			
			grad= this.dom.createElementNS(SVG.NS,"linearGradient");
			this.svgDefs.appendChild(grad);
			grad.setAttribute("id", "hgrad");
			grad.setAttribute("x1", "50%");
			grad.setAttribute("y1", "0%");
			grad.setAttribute("x2", "50%");
			grad.setAttribute("y2", "100%");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "5%");
			stop.setAttribute("stop-color","black");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "50%");
			stop.setAttribute("stop-color","lavender");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "85%");
			stop.setAttribute("stop-color","black");
			
			
			grad= this.dom.createElementNS(SVG.NS,"linearGradient");
			this.svgDefs.appendChild(grad);
			grad.setAttribute("id", "vgrad");
			grad.setAttribute("x1", "5%");
			grad.setAttribute("y1", "50%");
			grad.setAttribute("x2", "95%");
			grad.setAttribute("y2", "50%");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "5%");
			stop.setAttribute("stop-color","black");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "50%");
			stop.setAttribute("stop-color","green");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "85%");
			stop.setAttribute("stop-color","black");
			
			
			//the drawing itslef
			this.drawing= dom.createElementNS(SVG.NS,"g");
			this.drawing.setAttribute("transform", "translate("+MARGIN_LEFT+",0)");
			this.svgRoot .appendChild(drawing);
			
			
		} catch (ParserConfigurationException e)
			{
			throw new RuntimeException(e);
			}
		}
	
	public LTModel getModel()
		{
		return model;
		}
	
	public void setTrackHandler(Integer trackId,TrackHandler handler)
		{
		this.trackid2trackhandler.put(trackId, handler);
		}
	
	
	public void printSVGDocument(OutputStream out)
		{
		try
				{
				TransformerFactory factory=TransformerFactory.newInstance();
				Transformer transformer=factory.newTransformer();
				transformer.transform(new DOMSource(getSVGDocument()), new StreamResult(out));
				}
			catch (Exception e)
				{
				throw new RuntimeException(e);
				}
		}
	
	public Document getSVGDocument()
		{
		
		int panel_height = this.max_y;
		
		if(!flag_dirty)
			{
			LOG.info("Nothing was found");
			}
		if(panel_height<50)
			{
			this.max_y=50;
			panel_height=50;
			}
		
		this.svgRoot.setAttribute("width",String.valueOf(getPaneWidth()+MARGIN_LEFT));
		this.svgRoot.setAttribute("height",String.valueOf(panel_height));
		
		
		
		final int num_steps=10;
		int step=(int)(Math.pow(10,Math.ceil(Math.log10(chrom_length)))/num_steps);
		if(step>=10 && step*2>=chrom_length)
			{
			step/=10;
			}
		
		if(step>0)
			{
			//create a scale with vertical bars
			Element gscale= dom.createElementNS(SVG.NS, "g");
			drawing.insertBefore( gscale,drawing.getFirstChild());
			gscale.setAttribute("style", "stroke:none;");
			int ticks = chrom_start - chrom_start%step;
			
			while(ticks<= chrom_start+chrom_length)
				{
				float x= (float)base2pixel(ticks);
				if(x>=0 && x<getPaneWidth())
					{
					Element line= dom.createElementNS(SVG.NS, "rect");
					line.setAttribute("style","fill:white;");
					line.setAttribute("x", String.valueOf(x));
					line.setAttribute("y", "0");
					line.setAttribute("width", "2");
					line.setAttribute("height", String.valueOf(panel_height));
					gscale.appendChild(line);
					line= dom.createElementNS(SVG.NS, "rect");
					line.setAttribute("style","fill:gray;");
					line.setAttribute("x", String.valueOf(x));
					line.setAttribute("y", "0");
					line.setAttribute("width", "1");
					line.setAttribute("height", String.valueOf(panel_height));
					gscale.appendChild(line);
					
					Element t=  dom.createElementNS(SVG.NS, "text");
					t.appendChild(dom.createTextNode(niceNumber(ticks)));
					t.setAttribute("style", "stroke:black;font-size:9pt;");
					t.setAttribute("x", "0");
					t.setAttribute("y", "0");
					t.setAttribute("transform", "translate("+(x+2)+",10) rotate(90)");
					gscale.appendChild(t);
					}
				
				if(step>=10)
					{
					for(int t2=ticks;t2<ticks+step;t2+=step/10)
						{
						float x2= (float)(((t2-chrom_start)/(double)chrom_length)*getPaneWidth());
						if(!(x2>=0 && x2<getPaneWidth())) continue;
						Element line2= dom.createElementNS(SVG.NS, "line");
						line2.setAttribute("style","stroke:gray;");
						line2.setAttribute("x1", String.valueOf(x2));
						line2.setAttribute("y1", "0");
						line2.setAttribute("x2", String.valueOf(x2));
						line2.setAttribute("y2", String.valueOf(panel_height));
						gscale.appendChild(line2);
						}
					}
				
				ticks+=step;
				}
			}
		//a background for the drawing
		Element back= dom.createElementNS(SVG.NS,"rect");
		this.drawing.insertBefore(back,this.drawing.getFirstChild());
		back.setAttribute("x", "0");
		back.setAttribute("y", "0");
		back.setAttribute("width", String.valueOf(getPaneWidth()));
		back.setAttribute("height",String.valueOf(panel_height));
		back.setAttribute("style","fill:url(#background);stroke:black;");
		
		
		
		return dom;
		}
	
	public Track getCurrentTrack()
		{
		return currentTrack;
		}
	
	public ChromInfo getChromosome()
		{
		return chromosome;
		}
	
	public Organism getOrganism()
		{
		return this.organism;
		}
	
	public void setPaneWidth(Integer paneWidth)
		{
		if(pane_width!=null) throw new java.lang.IllegalStateException("width already set");
		pane_width = paneWidth;
		}
	
	public Integer getPaneWidth()
		{
		if(this.pane_width==null) this.pane_width=DEFAULT_PANE_WIDTH;
		return pane_width;
		}

	public TrackHandler getTrackHandlerFromTrackId(int track_id)
		{
		TrackHandler h= this.trackid2trackhandler.get(track_id);
		if(h==null)
				{
				h=new DefaultTrackHandler();
				this.trackid2trackhandler.put(track_id, h);
				}
		return h;
		}
	
	protected LTScannerImpl createScannerForTrack(final Track track)
		{
		LTScannerImpl scanner= new LTScannerImpl();
		scanner.track=track;
		return scanner;
		}
	
	public void scanTrack(
			Transaction txn,
			final Track track
			)
		throws DatabaseException
		{
		this.currentTrack=track;
		TrackChrom tc=new TrackChrom(track.getId(),getChromosome().getId());
		Long rootId= getModel().getTrackChrom2NodeId().get(txn, tc);
		if(rootId==null || rootId==LTNode.NIL) return;
		
		
		
		LTNode root= getModel().findNodeById(txn, rootId);
		root.getMaxEntitiesLength();
		LTScannerImpl scanner= createScannerForTrack(track);
		scanner.max_object_length= root.getMaxEntitiesLength();
		getModel().scan(txn,scanner);
		Element rez=drawTrack(txn,track,scanner);
		if(rez!=null)
			{
			this.flag_dirty=true;
			this.drawing.appendChild(rez);
			}
		else
			{
			LOG.info("Nothing for track "+track.getName());
			}
		
		}
	

	
	protected URLCreator getURLCreator(Track t)
		{
		return getTrackHandlerFromTrackId(t.getId()).getUrlCreator();
		}
	
	protected String getURLForObject(MappedObject o)
		{
		URLCreator c= getURLCreator(getCurrentTrack());
		if(c==null) return null;
		return c.createURL(
				getOrganism(),
				getChromosome(),
				getCurrentTrack(),
				o);
		}
	
	protected String getStyleForObject(MappedObject o)
		{
		return null;
		}
	
	
	
	
	
	/** retrieves a histogram painter for the given track */
	private HistogramPainter getHistogramPainter(Track track)
		{
		return getTrackHandlerFromTrackId(track.getId()).
			getHistogramPainter();
		}
	
	protected Packer createPacker(Track t)
		{
		Packer p= getTrackHandlerFromTrackId(t.getId()).getPacker();
		p.setEntityHeight(FEATURE_HEIGHT);
		return p;
		}
	
	protected EntityPainter createPainter(Track t)
		{
		EntityPainter p= getTrackHandlerFromTrackId(t.getId()).getEntityPainter();
		
		p.setChromLength(this.chrom_length);
		p.setChromStart(this.chrom_start);
		p.setOrganism(this.organism);
		p.setTrack(getCurrentTrack());
		p.setChromosome(getChromosome());
		p.setPaneWidth(this.getPaneWidth());
		p.setFontSize(LABEL_FONT_SIZE);
		return p;
		}
	
	
	protected Element drawTrack(Transaction txn,Track track,LTScannerImpl scanner)
		{
		final int starting_y= this.max_y;
		//final int histogram_height=100;
		//if(scanner.max_histogram_count==0 && scanner.lines.isEmpty()) return null;
		if(scanner.ltHistogram.getMaximum()==0 && scanner._mappedXY.isEmpty()) return null;
		
		Element g1= dom.createElementNS(SVG.NS,"g");
		//g1.setAttribute("style","fill:url(#grad"+j+");stroke:black;");
		g1.setAttribute("style","fill:blue;stroke:black;");
		g1.setAttribute("title",track.getName());
		
		Packer packer=createPacker(track);
		packer.pack(scanner._mappedXY);
		EntityPainter etpainter= createPainter(track);
		for(MappedXY o: scanner._mappedXY)
			{
			Element painted=etpainter.paintEntity(this.dom, o, starting_y);
			if(painted==null) continue;
			
			g1.appendChild(painted);
				
			this.max_y= Math.max(
					this.max_y,
					starting_y+o.getBounds().y+o.getBounds().height+2
					);
			}
		
		//draw horizontal ticks
		if( scanner.ltHistogram.getMaximum()>0)
			{
			HistogramPainter painter=getHistogramPainter(track);
			Element histo2dom= painter.paint(this.dom, scanner.ltHistogram);
			if(histo2dom!=null)
				{
				histo2dom.setAttribute("transform", "translate(0,"+this.max_y+")");
				g1.appendChild(histo2dom);
				}
			
			this.max_y+=2+painter.getHeight();
			}
		
		final int track_height=Math.max(50,this.max_y-starting_y);
		this.max_y=starting_y+track_height;
		
		/** create LEFT margin with label */
		Element label= dom.createElementNS(SVG.NS,"g");
		label.setAttribute("title", track.getDescription());
		label.setAttribute("transform", "translate(-"+MARGIN_LEFT+",0)");
		g1.appendChild(label);
		
		Element rect= dom.createElementNS(SVG.NS,"rect");
		label.appendChild(rect);
		rect.setAttribute("x", String.valueOf(0));
		rect.setAttribute("y", String.valueOf(starting_y));
		rect.setAttribute("width", String.valueOf(MARGIN_LEFT+1));
		rect.setAttribute("height",String.valueOf(track_height));
		rect.setAttribute("style", "fill:lightgray;stroke:black;stroke-width:2px;");
		
		Element tit= dom.createElementNS(SVG.NS,"text");
		label.appendChild(tit);
		tit.appendChild(dom.createTextNode(track.getName()));
		tit.setAttribute("x", String.valueOf(MARGIN_LEFT-5));
		tit.setAttribute("y", String.valueOf(starting_y+(track_height)/2 +LABEL_FONT_SIZE/2-2));
		tit.setAttribute("text-anchor","end"); 
		return g1;
		}
	
	
	
	protected int base2pixel(final int genome_pos)
		{
		return (int)(((genome_pos- this.chrom_start)/(double)this.chrom_length)*getPaneWidth());
		}
	
	
	
	protected MappedXY createMappedXY(MappedObject o)
		{
		int left_box;
		int right_box;
		int side;
		
		left_box= base2pixel(o.getStart());
		if(left_box<0) left_box=0;
		right_box= base2pixel(o.getEnd());
		if(right_box>=getPaneWidth()) right_box=getPaneWidth();
		float name_size= (o.getName().length()+2) * LABEL_FONT_SIZE * FONT_TO_PIXEL_RATIO;
		if(name_size==0 )
			{
			side= MappedXY.LABEL_IGNORED;
			}
		else if(left_box==0 && right_box==getPaneWidth())
			{
			side= MappedXY.LABEL_ON_MID;
			}
		else if( (right_box-left_box) > name_size)
			{
			side= MappedXY.LABEL_ON_MID;
			}
		else if(name_size< left_box)
			{
			side= MappedXY.LABEL_ON_LEFT;
			left_box-=name_size;
			}
		else if(getPaneWidth()- right_box > name_size)
			{
			side= MappedXY.LABEL_ON_RIGHT;
			right_box+=name_size;
			}
		else
			{
			side= MappedXY.LABEL_IGNORED;
			}
		MappedXY xy= new DefaultMappedXY();
		xy.setObject(o);
		xy.setLabelFlag(side);
		Rectangle r=xy.getBounds();
		r.x=left_box;
		r.width=right_box-left_box;
		return xy;
		}
	
	
	protected void entity(LTScannerImpl scanner,final MappedObject inserted)
		{
		//can happen is object size was overlaping two LTNodes
		if(inserted.getEnd()< this.chrom_start) return;
		if(inserted.getStart()>= (this.chrom_start+this.chrom_length)) return;
		scanner._mappedXY.add(createMappedXY(inserted));
		}
	
	private String niceNumber(final int i)
		{
		String s=String.valueOf(i);
		StringBuilder b= new StringBuilder(s.length());
		for(int j=0;j< s.length();j++)
			{
			if(j!=0 && j%3==0) b.insert(0, ' ');
			b.insert(0,s.charAt(s.length()-1-j));
			}
		return b.toString();
		}
	
	protected void histogram(LTScannerImpl scanner,LTNode root)
		{
		scanner.ltHistogram.seen(root);
		}
	
		public static void main(String[] args)
			{
			Transaction txn=null;
			LTModel model=null;
		try
			{
			String orgName=null;
			String chromName=null;
			int start=0;
			int width=400;
			int end=Integer.MAX_VALUE;
			String dbHome=null;
			int optind=0;
			while(optind< args.length)
				{
				if(args[optind].equals("-h") ||
				   args[optind].equals("-help") ||
				   args[optind].equals("--help"))
					{
					System.err.println("Options:");
					System.err.println(" -h help; This screen.");
					System.err.println(" -db-home <berkeley-db-home>");
					System.err.println(" -o <org-name>");
					System.err.println(" -c <chrom-id>");
					System.err.println(" -s <start> default:0");
					System.err.println(" -e <end> default:"+end);
					System.err.println(" -w width default:"+width);
					return;
					}
				else if(args[optind].equals("-db-home"))
					{
					dbHome=args[++optind];
					}
				else if(args[optind].equals("-log-level"))
					{
					LOG.setLevel(Level.parse(args[++optind]));
					}
				else if(args[optind].equals("-o") || args[optind].equals("-org") || args[optind].equals("-organism"))
					{
					orgName=args[++optind];
					}
				else if(args[optind].equals("-s") || args[optind].equals("-start"))
					{
					start=Integer.parseInt(args[++optind]);
					}
				else if(args[optind].equals("-e") || args[optind].equals("-end"))
					{
					end=Integer.parseInt(args[++optind]);
					}
				else if(args[optind].equals("-c") || args[optind].equals("-chrom"))
					{
					chromName=args[++optind];
					}
				else if(args[optind].equals("-w") || args[optind].equals("-width"))
					{
					width=Integer.parseInt(args[++optind]);
					}
				else if(args[optind].equals("--"))
					{
					optind++;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					System.err.println("Unknown option "+args[optind]);
					return;
					}
				else 
					{
					break;
					}
				++optind;
				}
			if(dbHome==null)
				{
				System.err.println("db-home not specified.");
				return;
				}
			
			if(orgName==null)
				{
				System.err.println("organism not specified.");
				return;
				}
			if(chromName==null)
				{
				System.err.println("chromosome not specified.");
				return;
				}
			
			if(start>=end)
				{
				System.err.println("Bad start/end");
				return;
				}
			
			model= new LTModel();
			model.open(new File(dbHome), false);
			
			
			Organism organism= model.getOrganismByName(txn,orgName);
			if(organism==null)
				{
				System.err.println("Unknown organism:"+orgName);
				return;
				}

			ChromInfo ci=model.getChromsomeByName(txn,organism.getId(),chromName);
			
			if(ci==null)
				{
				System.err.println("Model doesn't contains \""+chromName+"\"");
				return;
				}
			
			if(end> ci.getLength())
				{
				end=ci.getLength();
				}
			
		
			LTSvg svg= new LTSvg(model,organism,ci, start, (end-start));
			svg.setPaneWidth(width);
			Walker<Integer,Track> w= model.getTracksDB().openWalker(txn);
			while(w.getNext()==OperationStatus.SUCCESS)
				{
				if(!model.getTrackChrom2NodeId().containsKey(txn,
						new TrackChrom(w.getKey(), ci.getId())))
					{
					LOG.info("no data for chrom/track");
					continue;
					}
				Track track=w.getValue();
				svg.scanTrack(txn, track);
				}
			w.close();
			
			svg.printSVGDocument(System.out);
			} 
		catch(Throwable err)
			{
			err.printStackTrace();
			}
		finally
			{
			if(model!=null) model.close();
			}
		}
	
	}
